#pragma once

#include <assert.h>
#include <memory>
#include <boost/iostreams/filter/symmetric.hpp>

namespace alpha { namespace crypto {

namespace detail {

template <typename Traits>
struct hash_impl
{
	typedef char char_type;
	hash_impl() { Traits::init(&context_); }
	bool filter(char const *&begin_in, char const *end_in, char *&begin_out, char *end_out, bool flush)
	{
		Traits::update(&context_, (boost::uint8_t const *)(begin_in), end_in - begin_in);
		begin_in = end_in;
		if (flush)
		{
			assert(end_out - begin_out == Traits::digest_length);
			Traits::final((boost::uint8_t *)begin_out, &context_);
			begin_out = end_out;
		}
		return false;
	}
	void close() {}
private:
	typename Traits::context_type context_;
};

} // namespace detail
	
template <typename Traits, typename Alloc = std::allocator<char> >
struct basic_hash: boost::iostreams::symmetric_filter<detail::hash_impl<Traits>, Alloc>
{
	enum {digest_length = Traits::digest_length};
	basic_hash()
		: boost::iostreams::symmetric_filter<detail::hash_impl<Traits>, Alloc>(digest_length)
	{}
	static void digest(char const *beg, char const *end, char *out)
	{
		detail::hash_impl<Traits> impl;
		impl.filter(beg, end, out, out + digest_length, true);
	}
};
BOOST_IOSTREAMS_PIPABLE(basic_hash, 1)

}} // namespace alpha::crypto
